#include "stdafx.h"

#include "hwinmenu.h"
#include "hwinexception.h"
#include "hwincontrol.h"

namespace harlinn
{
    namespace windows
    {
        // ----------------------------------------------------------------------
        // MenuItem
        // ----------------------------------------------------------------------
        HWIN_EXPORT MenuItem::MenuItem()
            : Base( ),
              checked(false),
              isDefault(false),
              disabled(false),
              grayed(false),
              highlighted(false),
              menuBarBreak(false),
              menuBreak(false),
              ownerDraw(false)
        {

        }

        HWIN_EXPORT MenuItem::~MenuItem( )
        {
            
        }


        HWIN_EXPORT std::shared_ptr<MenuItems> MenuItem::ParentMenuItems() const
        {
            auto menu = ParentMenu();
            if(menu)
            {
                auto result = menu->Items();
                return result;
            }
            else 
            {
                return nullptr;
            }
        }
        HWIN_EXPORT std::shared_ptr<Menu> MenuItem::ParentMenu() const
        {
            auto theParentMenu = parentMenu.lock();
            return theParentMenu;
        }

        HWIN_EXPORT int MenuItem::IndexOf( ) const
        {
            auto mnuItems = ParentMenuItems();
            if(mnuItems)
            {
                return mnuItems->IndexOf(As<MenuItem>());
            }
            return -1;
        }


        HWIN_EXPORT std::shared_ptr<BitmapHandle> MenuItem::Bitmap() const
        {
            return bitmap;
        }

        HWIN_EXPORT MenuItem& MenuItem::SetBitmap(std::shared_ptr<BitmapHandle> theBitmap)
        {
            if(bitmap != theBitmap)
            {
                bitmap = theBitmap;
                this->UpdateMenuItem();
            }
            return *this;
        }

        HWIN_EXPORT std::shared_ptr<BitmapHandle> MenuItem::CheckedBitmap() const
        {
            return checkedBitmap;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetCheckedBitmap(std::shared_ptr<BitmapHandle> theCheckedBitmap)
        {
            if(checkedBitmap != theCheckedBitmap)
            {
                checkedBitmap = theCheckedBitmap;
                this->UpdateMenuItem();
            }
            return *this;
        }


        HWIN_EXPORT std::shared_ptr<BitmapHandle> MenuItem::UncheckedBitmap() const
        {
            return uncheckedBitmap;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetUncheckedBitmap(std::shared_ptr<BitmapHandle> theUncheckedBitmap)
        {
            if(uncheckedBitmap != theUncheckedBitmap)
            {
                uncheckedBitmap = theUncheckedBitmap;
                this->UpdateMenuItem();
            }
            return *this;
        }

        HWIN_EXPORT bool MenuItem::IsChecked() const
        {
            return checked;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetChecked(bool value)
        {
            if(checked != value)
            {
                checked = value;
                this->UpdateMenuItem();
            }
            return *this;
        }

        HWIN_EXPORT bool MenuItem::IsDefault() const
        {
            return isDefault;
        }

        HWIN_EXPORT MenuItem& MenuItem::SetDefault(bool value)
        {
            if(isDefault != value)
            {
                isDefault = value;
                this->UpdateMenuItem();
            }
            return *this;
        }

        HWIN_EXPORT bool MenuItem::IsDisabled() const
        {
            return disabled;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetDisabled(bool value)
        {
            if(disabled != value)
            {
                disabled = value;
                this->UpdateMenuItem();
            }
            return *this;
        }

        HWIN_EXPORT bool MenuItem::IsEnabled() const
        {
            return (disabled || grayed) == false;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetEnabled(bool value)
        {
            return SetDisabled(value?false:true);
        }
            
        HWIN_EXPORT bool MenuItem::IsGrayed() const
        {
            return grayed;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetGrayed(bool value)
        {
            if(grayed != value)
            {
                grayed = value;
                this->UpdateMenuItem();
            }
            return *this;
        }
        HWIN_EXPORT bool MenuItem::IsHighlighted() const
        {
            return highlighted;
        }
        HWIN_EXPORT MenuItem& MenuItem::SetHighlighted(bool value)
        {
            if(highlighted != value)
            {
                highlighted = value;
                this->UpdateMenuItem();
            }
            return *this;
        }


        

        HWIN_EXPORT MenuItem& MenuItem::DoOnAdd()
        {
            auto menu = ParentMenu();
            if(menu)
            {
                MENUITEMINFO info;
                this->InitializeMenuItemInfo(info);

                auto index = IndexOf();

                menu->GetHandle()->InsertMenuItem(index,true,info);
            }
            return *this;
        }

        HWIN_EXPORT MenuItem& MenuItem::DoOnRemove()
        {
            auto menu = ParentMenu();
            if(menu)
            {
                auto index = IndexOf();
                if(index >= 0)
                {
                    if(menu->GetHandle()->IsMenu())
                    {
                        menu->GetHandle()->RemoveMenu(index,MF_BYPOSITION);
                    }
                }
            }
            return *this;
        }


        HWIN_EXPORT const MenuItem& MenuItem::InitializeMenuItemInfo(MENUITEMINFO& info) const
        {
            memset(&info,0,sizeof(MENUITEMINFO));
            info.cbSize = sizeof(MENUITEMINFO);
            info.fMask |= MIIM_DATA | MIIM_STATE | MIIM_FTYPE;
            info.dwItemData = (ULONG_PTR)this;

            if(bitmap)
            {
                info.fMask |= MIIM_BITMAP;
                info.hbmpItem = bitmap->GetHBITMAP();
            }
            if(checkedBitmap)
            {
                info.fMask |= MIIM_CHECKMARKS;
                info.hbmpChecked = checkedBitmap->GetHBITMAP();
            }
            if(uncheckedBitmap)
            {
                info.fMask |= MIIM_CHECKMARKS;
                info.hbmpUnchecked = uncheckedBitmap->GetHBITMAP();
            }
            if(checked)
            {
                info.fState |= MFS_CHECKED;
            }
            if(isDefault)
            {
                info.fState |= MFS_DEFAULT;
            }
            if(disabled)
            {
                info.fState |= MFS_DISABLED;
            }
            if(grayed)
            {
                info.fState |= MFS_GRAYED;
            }
            if(highlighted)
            {
                info.fState |= MFS_HILITE;
            }
            if(menuBarBreak)
            {
                info.fType |= MFT_MENUBARBREAK;
            }
            if(menuBreak)
            {
                info.fType |= MFT_MENUBREAK;
            }
            if(ownerDraw)
            {
                info.fType |= MFT_OWNERDRAW;
            }

            return *this;
        }

        HWIN_EXPORT const MenuItem& MenuItem::UpdateMenuItem() const
        {
            auto menuItems = ParentMenuItems();
            if(menuItems)
            {
                MENUITEMINFO info;
                this->InitializeMenuItemInfo(info);
                int index = IndexOf();
                auto menu = ParentMenu();
                menu->GetHandle()->SetMenuItemInfo(index,true,&info);
            }
            return *this;
        }


        HWIN_EXPORT void MenuItem::DoOnMenuCommand(Message& message)
        {
            OnClick( this );
        }

        HWIN_EXPORT void MenuItem::DoOnMeasureItem(MEASUREITEMSTRUCT& measureItemStruct)
        {
            OnMeasureItem(this,measureItemStruct);
        }
        HWIN_EXPORT void MenuItem::DoOnDrawItem(DRAWITEMSTRUCT& drawItemStruct)
        {
            OnDrawItem(this,drawItemStruct);
        }

        
        // ----------------------------------------------------------------------
        // TextMenuItem
        // ----------------------------------------------------------------------
        HWIN_EXPORT TextMenuItem::TextMenuItem(const wchar_t* theText)
            : Base(), text(theText)
        {}
        HWIN_EXPORT TextMenuItem::TextMenuItem(const String& theText)
            : Base(), text(theText)
        {}

        HWIN_EXPORT const MenuItem& TextMenuItem::InitializeMenuItemInfo(MENUITEMINFO& info) const
        {
            Base::InitializeMenuItemInfo(info);
            info.fMask |= MIIM_STRING;
            info.cch = (UINT)text.length();
            info.dwTypeData = (LPTSTR)text.c_str();
            return *this;
        }

        HWIN_EXPORT TextMenuItem& TextMenuItem::SetText(const wchar_t* theText)
        {
            text = theText;
            this->UpdateMenuItem();
            return *this;
        }
        HWIN_EXPORT TextMenuItem& TextMenuItem::SetText(const String& theText)
        {
            text = theText;
            this->UpdateMenuItem();
            return *this;
        }
        HWIN_EXPORT const String& TextMenuItem::Text() const
        {
            return text;
        }
        HWIN_EXPORT String TextMenuItem::Text()
        {
            return text;
        }



        // ----------------------------------------------------------------------
        // SeparatorMenuItem
        // ----------------------------------------------------------------------
        HWIN_EXPORT SeparatorMenuItem::SeparatorMenuItem( )
            : Base( )
        {}

        HWIN_EXPORT MenuItem& SeparatorMenuItem::DoOnAdd()
        {
            auto menu = ParentMenu();
            if(menu)
            {
                menu->GetHandle()->AppendMenu(MF_SEPARATOR,0,nullptr);
            }
            return *this;
        }



        // ----------------------------------------------------------------------
        // SubMenuItem
        // ----------------------------------------------------------------------
        HWIN_EXPORT SubMenuItem::SubMenuItem(const wchar_t* theText)
            : Base( ), text(theText)
        {}
        HWIN_EXPORT SubMenuItem::SubMenuItem(const String& theText)
            : Base( ), text(theText)
        {}


        HWIN_EXPORT std::shared_ptr<Menu> SubMenuItem::Menu() const
        {
            if(!menu)
            {
                auto hMenu = ::CreatePopupMenu();
                if(!hMenu)
                {
                    ThrowLastOSError();
                }
                auto menuHandle = std::make_shared<MenuHandle>(hMenu,true);

                menu = std::make_shared<harlinn::windows::Menu>(menuHandle);
                menu->AddStyle(MNS_NOTIFYBYPOS);
            }
            return menu;
        }

        HWIN_EXPORT std::shared_ptr<const MenuItems> SubMenuItem::Items() const
        {
            auto menu = Menu();
            if(menu)
            {
                return menu->Items();
            }
            return nullptr;
        }
        HWIN_EXPORT std::shared_ptr<MenuItems> SubMenuItem::Items()
        {
            auto menu = Menu();
            if(menu)
            {
                return menu->Items();
            }
            return nullptr;
        }


        HWIN_EXPORT SubMenuItem& SubMenuItem::Add(std::shared_ptr<MenuItem> menuItem)
        {
            auto theItems = Items();
            if(theItems)
            {
                theItems->Add(menuItem);
            }
            return *this;
        }

        HWIN_EXPORT SubMenuItem& SubMenuItem::Remove(std::shared_ptr<MenuItem> menuItem)
        {
            auto theItems = Items();
            if(theItems)
            {
                theItems->Remove(menuItem);
            }
            return *this;
        }

        HWIN_EXPORT std::shared_ptr<TextMenuItem> SubMenuItem::AddMenuItem(const wchar_t* theText)
        {
            auto theMenu = Menu();
            if(theMenu)
            {
                auto result = theMenu->AddMenuItem(theText);
                return result;
            }
            return nullptr;
        }
        HWIN_EXPORT std::shared_ptr<TextMenuItem> SubMenuItem::AddMenuItem(const String& theText)
        {
            auto theMenu = Menu();
            if(theMenu)
            {
                auto result = theMenu->AddMenuItem(theText);
                return result;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<SeparatorMenuItem> SubMenuItem::AddSeparator()
        {
            auto theMenu = Menu();
            if(theMenu)
            {
                auto result = theMenu->AddSeparator();
                return result;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<SubMenuItem> SubMenuItem::AddSubMenu(const wchar_t* theText)
        {
            auto theMenu = Menu();
            if(theMenu)
            {
                auto result = theMenu->AddSubMenu(theText);
                return result;
            }
            return nullptr;
        }
        HWIN_EXPORT std::shared_ptr<SubMenuItem> SubMenuItem::AddSubMenu(const String& theText)
        {
            auto theMenu = Menu();
            if(theMenu)
            {
                auto result = theMenu->AddSubMenu(theText);
                return result;
            }
            return nullptr;
        }



        HWIN_EXPORT const MenuItem& SubMenuItem::InitializeMenuItemInfo(MENUITEMINFO& info) const
        {
            Base::InitializeMenuItemInfo(info);

            info.fMask |= MIIM_STRING;
            info.cch = (UINT)text.length();
            info.dwTypeData = (LPTSTR)text.c_str();

            auto theMenu = Menu();
            info.fMask |= MIIM_SUBMENU;
            info.hSubMenu = theMenu->GetHandle()->GetHMENU();

            return *this;
        }


        // ----------------------------------------------------------------------
        // MenuItems
        // ----------------------------------------------------------------------
        HWIN_EXPORT MenuItems::MenuItems(harlinn::windows::Menu* theOwner)
            : owner(theOwner)
        {
        }

        HWIN_EXPORT MenuItems::~MenuItems( )
        {
            for (auto item:items)
            {
                item->parentMenu = std::weak_ptr<harlinn::windows::Menu>();
            }
        }


        HWIN_EXPORT std::shared_ptr<Menu> MenuItems::Menu() const
        {
            if(owner)
            {
                return owner->As<harlinn::windows::Menu>();
            }
            return std::shared_ptr<harlinn::windows::Menu>();
        }

        HWIN_EXPORT MenuItems& MenuItems::Add(std::shared_ptr<MenuItem> menuItem)
        {
            if(menuItem)
            {
                auto previousMenu = menuItem->ParentMenu();
                auto thisMenu = Menu();
                if(previousMenu != thisMenu)
                {
                    if(previousMenu)
                    {
                        previousMenu->Items()->Remove(menuItem);
                    }
                    menuItem->parentMenu = thisMenu;
                    items.push_back(menuItem);
                    menuItem->DoOnAdd();
                }
            }
            return *this;
        }

        HWIN_EXPORT MenuItems& MenuItems::Remove(std::shared_ptr<MenuItem> menuItem)
        {
            if(menuItem)
            {
                int index = IndexOf(menuItem);
                menuItem->DoOnRemove();
                menuItem->parentMenu = std::weak_ptr<harlinn::windows::Menu>();
                items.erase(items.begin()+index);
                
            }
            return *this;
        }


        HWIN_EXPORT std::shared_ptr<TextMenuItem> MenuItems::AddMenuItem(const wchar_t* theText)
        {
            auto theMenu = Menu();
            auto result = make_component<TextMenuItem>(theMenu,theText);
            Add(result);
            return result;
        }
        HWIN_EXPORT std::shared_ptr<TextMenuItem> MenuItems::AddMenuItem(const String& theText)
        {
            auto theMenu = Menu();
            auto result = make_component<TextMenuItem>(theMenu,theText);
            Add(result);
            return result;
        }

        HWIN_EXPORT std::shared_ptr<SeparatorMenuItem> MenuItems::AddSeparator()
        {
            auto theMenu = Menu();
            auto result = make_component<SeparatorMenuItem>(theMenu);
            Add(result);
            return result;
        }

        HWIN_EXPORT std::shared_ptr<SubMenuItem> MenuItems::AddSubMenu(const wchar_t* theText)
        {
            auto theMenu = Menu();
            auto result = make_component<SubMenuItem>(theMenu,theText);
            Add(result);
            return result;
        }
        HWIN_EXPORT std::shared_ptr<SubMenuItem> MenuItems::AddSubMenu(const String& theText)
        {
            auto theMenu = Menu();
            auto result = make_component<SubMenuItem>(theMenu,theText);
            Add(result);
            return result;
        }


        HWIN_EXPORT int MenuItems::IndexOf(std::shared_ptr<const MenuItem> menuItem) const
        {
            if(menuItem)
            {
                for(vector::size_type i = 0; i < items.size(); i++)
                {
                    auto item = items.at(i);
                    if(item == menuItem)
                    {
                        return (int)i;
                    }
                }
            }
            return -1;
        }

        HWIN_EXPORT std::shared_ptr<const MenuItem> MenuItems::Item(int position) const
        {
            if(position >= 0 && static_cast<vector::size_type>( position ) < items.size())
            {
                auto item = items.at(position);
                return item;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<MenuItem> MenuItems::Item(int position)
        {
            if(position >= 0 && static_cast<vector::size_type>( position ) < items.size())
            {
                auto item = items.at(position);
                return item;
            }
            return nullptr;
        }


        // ----------------------------------------------------------------------
        // Menu
        // ----------------------------------------------------------------------
        std::map<HMENU,Menu*> Menu::menuMap;

        HWIN_EXPORT void Menu::AddToMenuMap(HMENU hMenu, Menu* theMenu)
        {
            CheckPointerNotNull(theMenu);
            CheckPointerNotNull(hMenu);

            auto it = menuMap.find(hMenu);
            if(it == menuMap.end())
            {
                menuMap.insert(std::map<HMENU,Menu*>::value_type(hMenu,theMenu));
            }
        }
        HWIN_EXPORT void Menu::RemoveFromMenuMap(HMENU hMenu)
        {
            CheckPointerNotNull(hMenu);
            auto it = menuMap.find(hMenu);

            if(it != menuMap.end())
            {
                menuMap.erase(it);
            }

        }
        HWIN_EXPORT Menu* Menu::GetFromMenuMap(HMENU hMenu)
        {
            if(hMenu)
            {
                auto it = menuMap.find(hMenu);

                if(it != menuMap.end())
                {
                    auto result = (*it).second;
                    return result;
                }
            }
            return nullptr;
        }

        HWIN_EXPORT Menu::Menu(std::shared_ptr<MenuHandle> menuHandle)
            : Base( ), 
              handle( menuHandle )
        {
            if(menuHandle)
            {
                HMENU hMenu = menuHandle->GetHMENU();
                if(hMenu)
                {
                    AddToMenuMap(hMenu,this);
                }
            }
            items = std::make_shared<MenuItems>(this);
        }

        HWIN_EXPORT Menu::~Menu( )
        {
            if(handle)
            {
                HMENU hMenu = handle->GetHMENU();
                if(hMenu)
                {
                    RemoveFromMenuMap(hMenu);
                }
            }
            items->owner = nullptr;
        }

        std::shared_ptr<MenuHandle> Menu::GetHandle()
        {
            return handle;
        }


        HWIN_EXPORT std::shared_ptr<const MenuItems> Menu::Items() const
        {
            return items;
        }

        HWIN_EXPORT std::shared_ptr<MenuItems> Menu::Items()
        {
            return items;
        }

        HWIN_EXPORT std::shared_ptr<const MenuItem> Menu::Item(int position) const
        {
            return items->Item(position);
        }

        HWIN_EXPORT std::shared_ptr<MenuItem> Menu::Item(int position)
        {
            return items->Item(position);
        }


        HWIN_EXPORT Menu& Menu::AddStyle(DWORD style)
        {
            handle->AddStyle(style);
            return *this;
        }

        HWIN_EXPORT Menu& Menu::Add(std::shared_ptr<MenuItem> menuItem)
        {
            auto theItems = Items();
            if(theItems)
            {
                theItems->Add(menuItem);
            }
            return *this;
        }

        HWIN_EXPORT Menu& Menu::Remove(std::shared_ptr<MenuItem> menuItem)
        {
            auto theItems = Items();
            if(theItems)
            {
                theItems->Remove(menuItem);
            }
            return *this;
        }


        HWIN_EXPORT std::shared_ptr<TextMenuItem> Menu::AddMenuItem(const wchar_t* theText)
        {
            auto theItems = Items();
            if(theItems)
            {
                auto result = theItems->AddMenuItem(theText);
                return result;
            }
            return nullptr;
        }
        HWIN_EXPORT std::shared_ptr<TextMenuItem> Menu::AddMenuItem(const String& theText)
        {
            auto theItems = Items();
            if(theItems)
            {
                auto result = theItems->AddMenuItem(theText);
                return result;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<SeparatorMenuItem> Menu::AddSeparator()
        {
            auto theItems = Items();
            if(theItems)
            {
                auto result = theItems->AddSeparator();
                return result;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<SubMenuItem> Menu::AddSubMenu(const wchar_t* theText)
        {
            auto theItems = Items();
            if(theItems)
            {
                auto result = theItems->AddSubMenu(theText);
                return result;
            }
            return nullptr;
        }

        HWIN_EXPORT std::shared_ptr<SubMenuItem> Menu::AddSubMenu(const String& theText)
        {
            auto theItems = Items();
            auto result = theItems->AddSubMenu(theText);
            return result;
        }


        HWIN_EXPORT void Menu::DoOnInitMenu(Message& message)
        {
            OnInitMenu(message);
        }

        HWIN_EXPORT void Menu::DoOnInitMenuPopup(Message& message)
        {
            OnInitMenuPopup(message);
        }

        HWIN_EXPORT void Menu::DoOnMenuCommand(Message& message)
        {
            OnMenuCommand(message);
            if(!message.handled)
            {
                int position = (int)message.wParam;
                auto item = Item(position);
                if(item)
                {
                    item->DoOnMenuCommand(message);
                }
            }
        }




        // ----------------------------------------------------------------------
        // MenuBar
        // ----------------------------------------------------------------------
        HWIN_EXPORT std::shared_ptr<MenuHandle> MenuBar::CreateHandle()
        {
            HMENU hMenu = ::CreateMenu();
            if(!hMenu)
            {
                ThrowLastOSError();
            }
            auto result = std::make_shared<MenuHandle>(hMenu,true);
            return result;
        }

        HWIN_EXPORT MenuBar::MenuBar( )
            : Base(CreateHandle())
        {
            AddStyle(MNS_NOTIFYBYPOS);
        }

        // ----------------------------------------------------------------------
        // PopupMenu
        // ----------------------------------------------------------------------
        HWIN_EXPORT std::shared_ptr<MenuHandle> PopupMenu::CreateHandle()
        {
            HMENU hMenu = ::CreatePopupMenu();
            if(!hMenu)
            {
                ThrowLastOSError();
            }
            auto result = std::make_shared<MenuHandle>(hMenu,true);
            return result;
        }

        PopupMenuAlignment PopupMenu::GetDefaultAlignment()
        {
            if(::GetSystemMetrics(SM_MENUDROPALIGNMENT))
            {
                return PopupMenuAlignment::Right | PopupMenuAlignment::Top;
            }
            else
            {
                return PopupMenuAlignment::Left | PopupMenuAlignment::Top;
            }
        }

        HWIN_EXPORT PopupMenu::PopupMenu( )
            : Base(CreateHandle()),
              alignment( GetDefaultAlignment() )
        {
            AddStyle(MNS_NOTIFYBYPOS);
        }

        HWIN_EXPORT PopupMenuAlignment PopupMenu::Alignment() const
        {
            return alignment;
        }
        HWIN_EXPORT PopupMenu& PopupMenu::SetAlignment(PopupMenuAlignment theAlignment)
        {
            if(alignment != theAlignment)
            {
                alignment = theAlignment;
            }
            return *this;
        }

        HWIN_EXPORT PopupMenu& PopupMenu::Show(std::shared_ptr<Control> theControl, const POINT& screenPosition)
        {
            HMENU hMenu = this->GetHandle()->GetHMENU();
            int flags = int(alignment);
            if(TrackPopupMenuEx(hMenu,flags,screenPosition.x,screenPosition.y,theControl->GetSafeHandle(),nullptr) == FALSE)
            {
                ThrowLastOSError();
            }
            return *this;
        }

        HWIN_EXPORT PopupMenu& PopupMenu::Show(std::shared_ptr<Control> theControl, const POINT& screenPosition, const RECT& excludeArea)
        {
            HMENU hMenu = this->GetHandle()->GetHMENU();
            int flags = int(alignment);
            TPMPARAMS tpmparams = {0,};
            tpmparams.cbSize = sizeof(TPMPARAMS);
            tpmparams.rcExclude = excludeArea;
            if(TrackPopupMenuEx(hMenu,flags,screenPosition.x,screenPosition.y,theControl->GetSafeHandle(),&tpmparams) == FALSE)
            {
                ThrowLastOSError();
            }
            return *this;
        }

    }
}